Representation Engineering: A Top-Down Approach to AI Transparency
概念に該当する位置にビット立つし、デコーダーモデルの場合は予測の直前トークン(入力プロンプトの末尾)にもビット立つ(図からも分かる)。
https://scrapbox.io/files/6731c3a930cd3e5e49aeeae5.png
reading vector: v
真実のモデルは誤った主張を避けますが、正直なモデルは真実であると考える主張
真実性は、モデルの出力とその真理値、つまり事実性との間の一貫性を評価すること
正直性は、モデルの出力とその内部信念との間の一貫性を評価すること
現在の誠実性評価では、通常、モデル出力の事実上の正確性のみをチェックし、2 種類の失敗を区別できない
2種類の失敗:モデルが誤った信念を表現すること、モデルが内部の信念を伝えない = 嘘つくこと
4.3.1 誠実性の抽出
誠実性の根本的な機能を抽出するために、セクション3.1で述べたLAT(線形活性化トレース)セットアップに従い、Azaria & Mitchell(2023)のデータセットから真実の文を用いて刺激を作成します。望ましいニューロン活動の分離性を高め、抽出を促進するために、LATの刺激セットには、不誠実さを基準タスク、誠実さを実験タスクとする例を含めています。具体的には、セクションD.1.2のタスクテンプレートを用いて、モデルに誠実または不誠実であるよう指示を与えます。
このセットアップにより、LATの読み取りベクトルが90%を超える分類精度で、モデルに誠実さまたは不誠実さを指示したホールドアウト例を区別することができました。これは、同一分布内での強い一般化能力を示しています。次に、誠実さや不誠実さの指示がなく、むしろ不誠実であるよう促される状況での分布外一般化を評価します(図10参照)。各層とトークン位置での活性化を可視化しました(図8参照)。各層に対し、全トークン位置で同じ読み取りベクトルを使用し、セクション3.1で述べた関数メソッドを用いて誠実性の表現を読み取ります。一つのシナリオではモデルが誠実であり、もう一つのシナリオでは不誠実になっています(セクションB.1参照)。スキャン用の入力は、どちらのシナリオもASSISTANT出力の最初の40トークンです。
特に、誠実さと不誠実さの間でニューロン活動に明確な対照が現れ、この技術が嘘検出に有用である可能性を示唆しています。
4.3.2 嘘と幻覚の検出
前のセクションの観察に基づき、各トークン位置での否定された誠実性スコアを複数の層にわたり合計することで、単純な嘘検出器を構築しました。最も読み取り性能が高い中間の20層を使用します。このトークンスコアは嘘検出器として利用でき、図9に示されています(より多くの例は図23参照)。興味深いことに、この指標は、意図的な虚偽、幻覚、誤解を招く情報の表現など、様々な形態の不誠実な行動を特定できることが確認されています。また、質問と回答のフォーマットが学習例とは異なることから、一般化能力を示しています。検出器の性能をさらに評価するため、図9に示すように、長いシナリオでのテストも行いました。以下の2つの観察をしました。
1. 最初のシナリオでは、モデルがD-を受け取ったと述べるとき、初めは誠実に見えます。しかし、そのトークンでモデルのロジットを詳細に分析すると、A、B、C、Dのトークンにそれぞれ11.3%、11.6%、37.3%、39.8%の確率を割り当てていることがわかります。Dが最も高い確率のトークンであるため出力されましたが、Cや他のオプションにもかなりの確率を割り当てており、不誠実な行動の可能性があることを示しています。さらに、2番目のシナリオでは、不誠実スコアが高いことが不誠実性の傾向の上昇と一致しており、誠実さや不誠実さの傾向がLLMにおいて分布的な特性を持ち、最終出力が必ずしも内部の思考過程を完全には反映しないことを示しています。
2. 検出器は、「もっと良い成績だと言う」や「高すぎて疑われるだろう」といった、嘘をついた結果について推測するフレーズも検出します。これは、嘘を検出するだけでなく、嘘をつく行為に関連するニューロン活動も特定することを示しています。また、不誠実な思考過程が様々な形で現れ、識別には専門的な検出手法が必要であることも示唆しています。
これらの観察により、読み取りベクトルが不誠実な思考過程や行動に対応していると確信できますが、嘘の検出には複雑さも伴うことが分かりました。包括的な評価には不誠実な行動のより詳細な探求が必要であり、これは今後の研究課題とします。
コードがあるのでとりあえず読んでみる。
examples/honesty/honesty.ipynbにある。
repe_pipeline_registryで特別なpipelineを作っている。
code:py
def repe_pipeline_registry():
PIPELINE_REGISTRY.register_pipeline(
"rep-reading",
pipeline_class=RepReadingPipeline,
pt_model=AutoModel,
)
PIPELINE_REGISTRY.register_pipeline(
"rep-control",
pipeline_class=RepControlPipeline,
pt_model=AutoModelForCausalLM,
)
pipelineはそのまま呼ばれると__call__が呼ばれる。self.preprocess,self.forward(内部で実質的に_foward),self.postprocessが呼ばれるのでpipelineに定義したこれらが呼ばれて推論走る。
honesty_rep_readerは取得した表現ベクトル。
ここにトレーニングしたベクトルが入っている。
code:python
honesty_rep_reader = rep_reading_pipeline.get_directions(
rep_token=rep_token,
hidden_layers=hidden_layers,
n_difference=n_difference,
direction_method=direction_method,
batch_size=32,
)
direction_signs↓
code:py
if train_labels is not None:
direction_finder.direction_signs = direction_finder.get_signs(
hidden_states, train_labels, hidden_layers)
方向の計算してる。project_onto_directionで何かプロンプトを入力した後の隠れ層の状態とControl Vector(学習によって得られたもの)の向きを計算してる。 呼び出し元でhidden_states[layer]を入力しているので、ここで入ってくるのは対象とするデータセット(学習後、どんな反応をするかみたいテキスト群)の個数がnになっている気がする。
この関数によってn個のスカラー値が返される。
code:py
def project_onto_direction(H, direction):
"""Project matrix H (n, d_1) onto direction vector (d_2,)"""
# Calculate the magnitude of the direction vector
# Ensure H and direction are on the same device (CPU or GPU)
if type(direction) != torch.Tensor:
H = torch.Tensor(H).cuda()
if type(direction) != torch.Tensor:
direction = torch.Tensor(direction)
direction = direction.to(H.device)
mag = torch.norm(direction)
assert not torch.isinf(mag).any()
# Calculate the projection
projection = H.matmul(direction) / mag
return projection
ちなみにproject_onto_directionに入ってくるHidden Stateはどの状態かというと、get_signs→get_directions→_batched_string_to_hiddensと追っていくと、ここでself(train_inputs...)(この__call__はたぶんgenerateじゃないので1トークンしかはかないやつ)してる内容がHidden Stateとして取得されているのでたぶんtrain文字列を入力したときのOutputを取得している。generateした後じゃない。つまりペンギンはセカイイチ早い鳥ですか?が入力された隠れ状態を取得するイメージ。
code:py
def _batched_string_to_hiddens(self, train_inputs, rep_token, hidden_layers, batch_size, which_hidden_states, **tokenizer_args):
# Wrapper method to get a dictionary hidden states from a list of strings
hidden_states_outputs = self(train_inputs, rep_token=rep_token,
hidden_layers=hidden_layers, batch_size=batch_size, rep_reader=None, which_hidden_states=which_hidden_states, **tokenizer_args)
hidden_states = {layer: [] for layer in hidden_layers}
for hidden_states_batch in hidden_states_outputs:
for layer in hidden_states_batch:
hidden_stateslayer.extend(hidden_states_batchlayer) return {k: np.vstack(v) for k, v in hidden_states.items()}
n_componentsはPCAのベクトルの数。基本は1と考えて良い(一番分散デカいやつのみ)。
projected_scoresで誠実な文章と不誠実な文章の2組のペアで並べる。
↑ここみると分かる。
例えば世界一速い鳥はペンギンですみたいなのが0(不誠実、False)の例。
labelはペアのうちどちらが誠実か?のインデックス。
地球は丸い、地球は四角いのペアの場合0が誠実なのでラベルは0になる。
outputs_minで誠実な文の方が小さいスコアになっているものに1立てる。
outputs_maxで誠実な文の方がデカいスコアになっているものに1立てる。
で両者の1の量を比べる。
前者の1が多いということはそのレイヤはスコアが小さい(誠実ベクトルとの射影が小さいほど)ほど誠実と判断されることになる。-1の「符号 = sign」にする。逆方向反応レイヤみたいな感じ。
可視化の際にはマイナスをメチャクチャ真っ赤にしてるので、この真っ赤になるすなわちマイナスの係数を得てしまったレイヤはよくないよねって判断になる。
code:py
def get_signs(self, hidden_states, train_choices, hidden_layers):
"""Given labels for the training data hidden_states, determine whether the
negative or positive direction corresponds to low/high concept
(and return corresponding signs -1 or 1 for each layer and component index)
NOTE: This method assumes that there are 2 entries in hidden_states per label,
aka len(hidden_stateslayer) == 2 * len(train_choices). For example, if n_difference=1, then hidden_states here should be the raw hidden states
rather than the relative (i.e. the differences between pairs of examples).
Args:
hidden_states: Hidden states of the model on the training data (per layer)
train_choices: Labels for the training data
hidden_layers: Layers to consider
Returns:
signs: A dict mapping layers to sign arrays (n_components,)
"""
signs = {}
if self.needs_hiddens and hidden_states is not None and len(hidden_states) > 0:
for layer in hidden_layers:
assert hidden_stateslayer.shape0 == 2 * len(train_choices), f"Shape mismatch between hidden states ({hidden_stateslayer.shape0}) and labels ({len(train_choices)})" for component_index in range(self.n_components):
projected_scores = [transformed_hidden_statesi:i+2 for i in range(0, len(transformed_hidden_states), 2)] outputs_min = [1 if min(o) == olabel else 0 for o, label in zip(projected_scores, train_choices)] outputs_max = [1 if max(o) == olabel else 0 for o, label in zip(projected_scores, train_choices)] signslayer.append(-1 if np.mean(outputs_min) > np.mean(outputs_max) else 1) else:
for layer in hidden_layers:
return signs
ここに戻ってくる。
ここまでの話の流れ的にH_testsはhidden_states(データセットを入れた後のレイヤの出力)を作っているっぽい。
で、トークン位置それぞれのhidden_statesを作っている(今回はある単一の文章に対するレイヤ出力)。
rep_tokenはどのトークンに着目するかの話。
https://scrapbox.io/files/6732bb345a6843fdc13531e2.png
repengだと最終にのみ着目していたけれど、今回はトークン単位の信念みたいのでトークン単位でhidden_statesの値を取る。 results[pos][0][layer][0]は単純にhidden_statesの値なので、それと上記で求めたどれくらい信念ベクトルとレイヤが近いかを表したdirection_signsをかけ合わせた値を単純にトークン単位の真実度とみなしている。
repeng的に最終トークンしかみないならもっと話は簡単になる。 code:py
chosen_idx = 0
input_ids = tokenizer.tokenize(chosen_str)
results = []
for ice_pos in range(len(input_ids)):
ice_pos = -len(input_ids) + ice_pos
rep_reader=honesty_rep_reader,
rep_token=ice_pos,
hidden_layers=hidden_layers)
results.append(H_tests)
honesty_scores = []
honesty_scores_means = []
for pos in range(len(results)):
tmp_scores = []
tmp_scores_all = []
for layer in hidden_layers:
tmp_scores_all.append(resultspos0layer0 * honesty_rep_reader.direction_signslayer0) if layer in layers:
honesty_scores.append(tmp_scores_all)
honesty_scores_means.append(np.mean(tmp_scores))
rep_reader_scores_dict'honesty' = honesty_scores rep_reader_scores_mean_dict'honesty' = honesty_scores_means